package org.rsbot.script;

import java.util.EventListener;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import org.rsbot.bot.Bot;
import org.rsbot.event.EventMulticaster;
import org.rsbot.event.listeners.PaintListener;
import org.rsbot.script.randoms.antiban.BreakHandler;
import org.rsbot.script.randoms.antiban.LoginBot;

public abstract class Script extends Methods implements EventListener {

    Set<Script> delegates = new HashSet<Script>();
    Methods methods;
    public int ID = -1;
    public volatile boolean isActive = false;
    public volatile boolean isPaused = false;
    private volatile boolean random = false;
    private volatile boolean ban = false;

    /**
     * The main loop. Called if you return true from main. Called until you
     * return a negative number.
     *
     * @return The number of milliseconds that the manager should wait before
     *         calling it again. Returning a negative number will stop the
     *         script.
     */
    public abstract int loop();

    /**
     * Perform any clean up such as unregistering any event listeners.
     */
    @Override
    public void onFinish() {
    }

    /**
     * Initializes this script with another script's context.
     *
     * @param script The context providing Script.
     * @see #delegateTo(Script)
     */
    public final void init(final Script script) {
        init(script.methods);
    }

    /**
     * Initializes this script with a given context.
     *
     * @param ctx The MethodContext.
     */
    public final void init(final Methods methods) {
        this.methods = methods;
    }

    /**
     * Initializes the provided script with this script's
     * method context and adds the delegate as a listener
     * to the event manager, allowing it to receive client
     * events. The script will be stored as a delegate of
     * this script and removed from the event manager when
     * this script is stopped. The onStart(), loop() and
     * onFinish() methods are not automatically called on
     * the delegate.
     *
     * @param script The script to delegate to.
     */
    public final void delegateTo(final Script script) {
        script.init(methods);
        methods.getBot().getEventManager().addListener(script);
        delegates.add(script);
    }

    /**
     * The start method. Called before loop() is first called. If <tt>false</tt>
     * is returned, the script will not start and loop() will never be called.
     *
     * @param map The arguments passed in from the description.
     * @return <tt>true</tt> if the script should be started.
     */
    public boolean onStart(final Map<String, String> map) {
        final String args = map.get("args");
        return args == null ? onStart(new String[0]) : onStart(args.split(","));
    }

    /**
     * The start method. Return true if you where able to start up successfully
     * with the given arguments.
     *
     * @param args A comma-separated list of arguments.
     * @return <tt>true</tt> if the script should be started.
     */
    public boolean onStart(final String[] args) {
        return true;
    }

    /**
     * For internal use only. Deactivates this script if
     * the appropriate id is provided.
     *
     * @param id The id from ScriptHandler.
     */
    public final void deactivate(final int id) {
        if (id != this.ID)
            throw new IllegalStateException("Invalid id!");
        this.isActive = false;
    }

    /**
     * For internal use only. Sets the pool id of this script.
     *
     * @param id The id from ScriptHandler.
     */
    public final void setID(final int id) {
        if (this.ID != -1)
            throw new IllegalStateException("Already added to pool!");
        this.ID = id;
    }

    /**
     * Pauses/resumes this script.
     *
     * @param paused <tt>true</tt> to pause; <tt>false</tt> to resume.
     */
    public final void setPaused(final boolean paused) {
        if (isActive && !random && !ban) {
            if (paused) {
                blockEvents(true);
            } else {
                unblockEvents();
            }
        }
        this.isPaused = paused;
    }

    /**
     * Returns whether or not this script is paused.
     *
     * @return <tt>true</tt> if paused; otherwise <tt>false</tt>.
     */
    public final boolean isPaused() {
        return isPaused;
    }

    public final void run(final Map<String, String> map) {
        methods.getBot().getEventManager().addListener(this);
        setupMenuListener();
        log.info("Script started.");
        boolean start = false;
        try {
            start = onStart(map);
        } catch (final ThreadDeath ignored) {
        } catch (final Throwable e) {
            log.log(Level.SEVERE, "Error starting script: ", e);
        }
        if (start) {
            isActive = true;
            try {
                while (isActive) {
                    if (!isPaused) {
                        if (checkForRandoms()) {
                            continue;
                        }
                        if (checkForAntiban()) {
                            continue;
                        }
                        int timeOut = -1;
                        try {
                            timeOut = loop();
                        } catch (final ThreadDeath td) {
                            break;
                        } catch (final Throwable e) {
                            log.log(Level.WARNING, "Uncaught exception from script: ", e);
                        }
                        if (timeOut == -1) {
                            break;
                        }
                        try {
                            wait(timeOut);
                        } catch (final ThreadDeath td) {
                            break;
                        } catch (final RuntimeException e) {
                            e.printStackTrace();
                            break;
                        }
                    } else {
                        try {
                            wait(1000);
                        } catch (final ThreadDeath td) {
                            break;
                        } catch (final RuntimeException e) {
                            e.printStackTrace();
                            break;
                        }
                    }
                }
                try {
                    onFinish();
                } catch (final ThreadDeath ignored) {
                } catch (final RuntimeException e) {
                    e.printStackTrace();
                }
            } catch (final ThreadDeath td) {
                onFinish();
            }
            isActive = false;
            log.info("Script stopped.");
        } else {
            log.severe("Failed to start up.");
        }
		for (final Script s : delegates) {
			methods.getBot().getEventManager().removeListener(s);
		}
        delegates.clear();
        methods.getBot().getEventManager().removeListener(this);
        methods.getBot().getScriptHandler().stopScript(ID);
        ID = -1;
    }

    private boolean checkForRandoms() {
        if (methods.getBot().disableRandoms)
            return false;
        for (final Random random : methods.getBot().getScriptHandler().getRandoms()) {
            if (random.isEnabled()) {
                if (random.activateCondition()) {
                    this.random = true;
                    blockEvents(false);
                    random.runRandom();
                    unblockEvents();
                    this.random = false;
                    return true;
                }
            }
        }
        return false;
    }

    private boolean checkForAntiban() {
        for (final Antiban antiban : methods.getBot().getScriptHandler().getAntiban()) {
            if (antiban instanceof BreakHandler) {
                if (methods.getBot().disableBreakHandler) {
                    continue;
                }
            }
            if (antiban instanceof LoginBot) {
                if (methods.getBot().disableAutoLogin) {
                    continue;
                }
            } else if (methods.getBot().disableAntibans) {
                continue;
            }
            if (antiban.isEnabled() && antiban.activateCondition()) {
				this.ban = true;
				blockEvents(false);
				antiban.runAntiban();
				unblockEvents();
				this.ban = false;
				return true;
            }
        }
        return false;
    }

    private void blockEvents(final boolean paint) {
        for (final Script s : delegates) {
            methods.getBot().getEventManager().removeListener(s);
            if (paint && s instanceof PaintListener) {
                methods.getBot().getEventManager().addListener(s, EventMulticaster.PAINT_EVENT);
            }
        }
        methods.getBot().getEventManager().removeListener(this);
        if (paint && this instanceof PaintListener) {
            methods.getBot().getEventManager().addListener(this, EventMulticaster.PAINT_EVENT);
        }
    }

    private void unblockEvents() {
        for (final Script s : delegates) {
            methods.getBot().getEventManager().removeListener(s);
            methods.getBot().getEventManager().addListener(s);
        }
        methods.getBot().getEventManager().removeListener(this);
        methods.getBot().getEventManager().addListener(this);
    }
    
    /**
     * @return true if loop() isn't looping at the moment
     */
    public boolean loopInactive() {
    	return !isActive || isPaused || ban || random;
    }
}
